Building a Python File Automation or Expense Tracker Project
Course: Python Training
Topic: Building a Python File Automation or Expense Tracker Project
Course Link: Python Training Course at JustAcademy
Demo Link: Register for a Course Demo
Website: JustAcademy Homepage
1. Introduction to Python Project Development
Building a practical project is an effective way to combine Python programming concepts such as variables, functions, loops, conditional statements, exception handling, file operations, data structures, and modules.
Two useful beginner-to-intermediate projects are a File Automation System and an Expense Tracker. A file automation system organizes files and folders automatically, while an expense tracker records expenses, stores transactions, and calculates spending summaries.
Both projects demonstrate how Python can solve everyday problems and provide practical experience for a portfolio, internship, or entry-level programming role.
Learning Objectives
- Plan and structure a real-world Python project.
- Use
pathlib and shutil for file and folder operations.
- Read, write, and update CSV or JSON files.
- Build an expense tracker with transaction records and summaries.
- Validate user input and handle exceptions.
- Create reusable functions and organize code into modules.
- Test functionality and document a completed project.
2. Understanding the Two Project Options
| Feature |
File Automation Project |
Expense Tracker Project |
| Main purpose |
Organize and manage files automatically. |
Record and analyze personal expenses. |
| Python modules |
pathlib, shutil, os, datetime |
csv, datetime, decimal, pathlib |
| Data storage |
Filesystem and optional activity log. |
CSV or JSON transaction file. |
| Key operations |
Sort, rename, copy, archive, and report. |
Add, view, filter, summarize, and export expenses. |
| Difficulty |
Beginner to intermediate. |
Beginner to intermediate. |
| Possible extension |
Scheduled backups and duplicate detection. |
Monthly budgets and category reports. |
3. Project One: Building a Python File Automation System
3.1 What Is File Automation?
File automation means using a program to perform repetitive file-related tasks without manually handling each file. For example, a downloads folder may contain images, PDFs, spreadsheets, documents, and compressed files. A Python script can classify these files into folders based on their extensions.
Python's pathlib module provides a convenient way to work with paths, while shutil provides higher-level operations for copying, moving, and managing files.
3.2 Project Requirements
- Read files from a selected source folder.
- Identify file types by extension.
- Create destination folders automatically.
- Move files into appropriate folders.
- Avoid unintentionally overwriting existing files.
- Display a summary of completed operations.
- Optionally log actions for auditing and troubleshooting.
3.3 Suggested Folder Structure
file_automation/
├── main.py
├── file_organizer.py
├── logger_config.py
├── README.md
├── tests/
│ └── test_file_organizer.py
└── sample_files/
├── report.pdf
├── photo.jpg
├── data.csv
└── notes.txt
Keeping functionality in separate modules makes the application easier to test, extend, and maintain.
3.4 Import the Required Modules
from pathlib import Path
import shutil
import logging
Path represents filesystem paths and provides methods for listing, checking, and creating files or directories.
shutil provides high-level file operations such as copying and moving.
logging records program activity and diagnostic messages.
3.5 Create a File Organizer
from pathlib import Path
import shutil
def organize_files(source_folder):
source = Path(source_folder).expanduser()
if not source.exists():
raise FileNotFoundError(f"Folder does not exist: {source}")
if not source.is_dir():
raise NotADirectoryError(f"Not a directory: {source}")
categories = {
"Images": {".jpg", ".jpeg", ".png", ".gif", ".webp"},
"Documents": {".pdf", ".docx", ".doc", ".txt"},
"Spreadsheets": {".csv", ".xlsx", ".xls"},
"Archives": {".zip", ".rar", ".7z"}
}
moved_count = 0
for item in sorted(source.iterdir()):
if not item.is_file():
continue
category = "Others"
for folder_name, extensions in categories.items():
if item.suffix.lower() in extensions:
category = folder_name
break
destination_folder = source / category
destination_folder.mkdir(exist_ok=True)
destination = destination_folder / item.name
# Never overwrite an existing destination file.
if destination.exists():
print(f"Skipped existing destination: {item.name}")
continue
shutil.move(str(item), str(destination))
moved_count += 1
print(f"Moved {item.name} to {category}")
print(f"Organization complete. Files moved: {moved_count}")
if __name__ == "__main__":
organize_files("sample_files")
3.6 How the File Organizer Works
- Accepts a folder path: The program receives the folder it should organize.
- Validates the folder: It checks that the path exists and points to a directory.
- Lists files: It examines the direct contents of the source directory.
- Checks extensions: It uses the file suffix to determine the destination category.
- Creates folders: It creates destination directories when necessary.
- Prevents overwrites: It skips a file if the same destination filename already exists.
- Moves files: It moves eligible files into their category folders.
- Reports the result: It prints the number of files moved.
Important: The script changes the filesystem. Test it on a disposable sample folder before using it on important files. It only processes the source folder's immediate files, not files inside nested directories.
3.7 Adding Logging to the File Automation System
Logging helps record what the program did and makes errors easier to investigate.
import logging
logging.basicConfig(
filename="file_automation.log",
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s"
)
logging.info("File automation started")
logging.warning("An existing destination file was skipped")
logging.error("A file operation failed")
In a complete application, place log statements inside the file-processing functions and record the filename, operation, and error details when a task fails.
3.8 Handling Errors Safely
from pathlib import Path
import shutil
def move_file_safely(source, destination):
source_path = Path(source)
destination_path = Path(destination)
try:
if not source_path.is_file():
raise FileNotFoundError(f"Source file not found: {source_path}")
destination_path.parent.mkdir(parents=True, exist_ok=True)
if destination_path.exists():
print("Destination already exists. Skipping.")
return False
shutil.move(str(source_path), str(destination_path))
print("File moved successfully.")
return True
except OSError as error:
print(f"File operation failed: {error}")
return False
Filesystem operations may fail because of permissions, locked files, invalid paths, insufficient storage, or device errors. Handle expected errors and provide clear messages instead of allowing the program to fail without explanation.
3.9 Optional File Automation Features
- Duplicate detection: Compare file hashes to identify files with identical content.
- Automatic backups: Copy selected files into a dated backup folder.
- File renaming: Rename files using a consistent naming convention.
- File reporting: Generate a CSV report of filenames, extensions, sizes, and modification dates.
- Archive creation: Use
zipfile or shutil.make_archive() to create compressed backups.
- Dry-run mode: Display planned changes without moving files.
- Command-line arguments: Use
argparse to accept the source folder and options from the terminal.
4. Project Two: Building a Python Expense Tracker
4.1 What Is an Expense Tracker?
An expense tracker is an application that records money spent on different activities, such as groceries, transport, rent, entertainment, and utilities. It stores transaction details and calculates totals to help users understand their spending habits.
A beginner-friendly expense tracker can use a CSV file for persistent storage. The built-in csv module supports tabular data, while decimal.Decimal provides more appropriate decimal arithmetic for monetary values than ordinary binary floating-point arithmetic.
4.2 Project Requirements
- Add a new expense with a date, category, description, and amount.
- Save each transaction to a CSV file.
- Display all recorded expenses.
- Calculate total spending.
- Calculate spending by category.
- Filter expenses by month or category.
- Validate amounts, dates, and required fields.
- Handle missing or malformed data safely.
4.3 Suggested Folder Structure
expense_tracker/
├── main.py
├── expense_manager.py
├── reports.py
├── expenses.csv
├── README.md
└── tests/
└── test_expenses.py
4.4 Define the Expense Data Model
Each expense can be represented by a dictionary containing four fields:
| Field |
Example |
Purpose |
| Date |
2026-10-22 |
Records when the expense occurred. |
| Category |
Food |
Groups expenses by type. |
| Description |
Lunch |
Explains what the payment was for. |
| Amount |
250.00 |
Stores the expense amount. |
4.5 Create the Expense Tracker Program
The following example provides a functional command-line expense tracker with CSV storage, input validation, transaction listing, and category summaries.
import csv
from pathlib import Path
from datetime import date
from decimal import Decimal, InvalidOperation
DATA_FILE = Path("expenses.csv")
FIELDNAMES = ["date", "category", "description", "amount"]
def load_expenses():
if not DATA_FILE.exists():
return []
with DATA_FILE.open("r", newline="", encoding="utf-8") as file:
reader = csv.DictReader(file)
if reader.fieldnames != FIELDNAMES:
raise ValueError("Expense CSV has an unexpected header.")
expenses = []
for row in reader:
try:
expense_date = date.fromisoformat(row["date"])
category = row["category"].strip()
description = row["description"].strip()
amount = Decimal(row["amount"])
if not category or not description:
raise ValueError("Category and description are required.")
if not amount.is_finite() or amount <= 0:
raise ValueError("Amount must be a positive finite number.")
expenses.append({
"date": expense_date.isoformat(),
"category": category,
"description": description,
"amount": amount
})
except (ValueError, InvalidOperation, TypeError) as error:
raise ValueError(f"Invalid expense row: {row}") from error
return expenses
def save_expenses(expenses):
temporary_file = DATA_FILE.with_suffix(".tmp")
with temporary_file.open("w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(file, fieldnames=FIELDNAMES)
writer.writeheader()
for expense in expenses:
writer.writerow({
"date": expense["date"],
"category": expense["category"],
"description": expense["description"],
"amount": format(expense["amount"], ".2f")
})
temporary_file.replace(DATA_FILE)
def add_expense(expenses):
try:
expense_date = input("Date (YYYY-MM-DD, blank for today): ").strip()
if not expense_date:
expense_date = date.today().isoformat()
else:
expense_date = date.fromisoformat(expense_date).isoformat()
category = input("Category: ").strip()
description = input("Description: ").strip()
amount = Decimal(input("Amount: ").strip())
if not category or not description:
raise ValueError("Category and description are required.")
if not amount.is_finite() or amount <= 0:
raise ValueError("Amount must be a positive finite number.")
expense = {
"date": expense_date,
"category": category,
"description": description,
"amount": amount
}
updated_expenses = expenses + [expense]
save_expenses(updated_expenses)
expenses.append(expense)
print("Expense saved successfully.")
except (ValueError, InvalidOperation) as error:
print(f"Invalid expense: {error}")
def list_expenses(expenses):
if not expenses:
print("No expenses recorded.")
return
print("\nDate | Category | Description | Amount")
print("-" * 55)
for expense in expenses:
print(
f"{expense['date']} | {expense['category']} | "
f"{expense['description']} | {expense['amount']:.2f}"
)
def show_summary(expenses):
total = sum(
(expense["amount"] for expense in expenses),
Decimal("0.00")
)
category_totals = {}
for expense in expenses:
category = expense["category"]
category_totals[category] = (
category_totals.get(category, Decimal("0.00"))
+ expense["amount"]
)
print(f"\nTotal expenses: {total:.2f}")
print("\nSpending by category:")
if not category_totals:
print("No expense data available.")
return
for category, amount in sorted(category_totals.items()):
print(f"{category}: {amount:.2f}")
def main():
try:
expenses = load_expenses()
except (OSError, ValueError, csv.Error) as error:
print(f"Could not load expense data: {error}")
return
while True:
print("\n--- Expense Tracker ---")
print("1. Add expense")
print("2. List expenses")
print("3. Show summary")
print("4. Exit")
choice = input("Choose an option: ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
list_expenses(expenses)
elif choice == "3":
show_summary(expenses)
elif choice == "4":
print("Goodbye!")
break
else:
print("Invalid option. Please choose 1 to 4.")
if __name__ == "__main__":
main()
4.6 How the Expense Tracker Works
- Load existing data: The program reads saved records from
expenses.csv when the application starts.
- Display a menu: Users choose whether to add an expense, view transactions, see a summary, or exit.
- Validate input: The program checks dates, categories, descriptions, and amounts.
- Save transactions: Expense records are written to CSV storage.
- Display records: Users can review previously recorded expenses.
- Calculate totals: The program calculates total spending and aggregates amounts by category.
The example uses a temporary file before replacing the main CSV file to reduce the risk of leaving a partially written file. For a production application, consider stronger backup and recovery strategies, especially if several processes may update the same data.
4.7 Example Expense Data
date,category,description,amount
2026-10-01,Food,Groceries,1200.00
2026-10-03,Transport,Bus pass,500.00
2026-10-05,Food,Lunch,250.00
2026-10-07,Utilities,Electricity bill,1500.00
Using the sample data above, total expenses are 3450.00. Food accounts for 1450.00, Transport accounts for 500.00, and Utilities accounts for 1500.00. These amounts are illustrative and use no particular currency assumption.
5. Understanding CSV and JSON Storage
Both CSV and JSON are useful for saving data between program runs, but they serve slightly different purposes.
| Feature |
CSV |
JSON |
| Structure |
Rows and columns. |
Objects, arrays, and nested values. |
| Best suited for |
Tabular records and spreadsheet workflows. |
Structured application data and configuration. |
| Human readability |
Good for simple tables. |
Good for structured data. |
| Python module |
csv |
json |
| Typical limitation |
Limited support for nested data types. |
Requires a clear schema and careful type conversion. |
Example: Saving Data as JSON
import json
from pathlib import Path
settings = {
"currency": "INR",
"monthly_budget": 15000,
"categories": ["Food", "Transport", "Utilities"]
}
file_path = Path("settings.json")
file_path.write_text(
json.dumps(settings, indent=2),
encoding="utf-8"
)
loaded_settings = json.loads(
file_path.read_text(encoding="utf-8")
)
print(loaded_settings["monthly_budget"])
This example demonstrates JSON serialization and deserialization. For an expense tracker, amounts should be converted and validated deliberately when loading and saving records. JSON does not automatically preserve Python's Decimal type.
6. Improving the Expense Tracker
6.1 Filter Expenses by Month
def filter_by_month(expenses, year, month):
prefix = f"{year:04d}-{month:02d}-"
return [
expense for expense in expenses
if expense["date"].startswith(prefix)
]
# Example:
# october_expenses = filter_by_month(expenses, 2026, 10)
Filtering lets users review spending for a specific month. Validate that the month is between 1 and 12 before calling this function with user-provided values.
6.2 Add a Monthly Budget
from decimal import Decimal
def check_budget(monthly_expenses, budget):
total = sum(
(expense["amount"] for expense in monthly_expenses),
Decimal("0.00")
)
remaining = budget - total
print(f"Monthly spending: {total:.2f}")
print(f"Budget remaining: {remaining:.2f}")
if remaining < 0:
print("Warning: Monthly budget exceeded.")
else:
print("You are within your budget.")
Store the budget as a validated Decimal value, and make sure the selected expenses belong to the relevant month before calculating the budget status.
6.3 Export a Summary Report
import csv
from pathlib import Path
def export_category_report(category_totals):
output_path = Path("expense_summary.csv")
with output_path.open(
"w",
newline="",
encoding="utf-8"
) as file:
writer = csv.writer(file)
writer.writerow(["Category", "Total"])
for category, amount in sorted(category_totals.items()):
writer.writerow([category, format(amount, ".2f")])
print(f"Report saved to {output_path.resolve()}")
This function expects a dictionary of category totals, such as the dictionary generated by the summary function. A report export is useful for spreadsheet analysis and record keeping.
7. Testing the Projects
Testing checks that the application behaves correctly for valid input, invalid input, missing files, and unexpected conditions. Small functions are easier to test than a program where all logic is placed inside one large loop.
File Automation Test Checklist
- Confirm that files are moved to the correct folders.
- Confirm that file extensions are matched without case sensitivity.
- Confirm that an existing destination file is not overwritten.
- Test missing source folders and invalid paths.
- Test read-only folders or permission errors where practical.
- Verify that subfolders are not unintentionally processed or moved.
- Check that a dry-run mode makes no filesystem changes.
Expense Tracker Test Checklist
- Add a valid expense and confirm it is saved.
- Reject a negative amount, zero amount, or invalid numeric input.
- Reject malformed dates and missing required fields.
- Confirm that totals and category summaries are correct.
- Check behavior when the CSV file does not yet exist.
- Test malformed CSV rows and unexpected headers.
- Confirm that saved data can be loaded again after restarting the program.
Example Unit Test
import unittest
from decimal import Decimal
def calculate_total(amounts):
return sum(amounts, Decimal("0.00"))
class ExpenseTests(unittest.TestCase):
def test_calculate_total(self):
amounts = [
Decimal("100.00"),
Decimal("50.50"),
Decimal("25.00")
]
self.assertEqual(
calculate_total(amounts),
Decimal("175.50")
)
if __name__ == "__main__":
unittest.main()
Save this code in a test file and run it with Python. The test checks the calculation independently of file storage or user input.
8. Common Errors and Troubleshooting
| Problem |
Possible Cause |
Solution |
FileNotFoundError |
The specified path does not exist. |
Validate the path and confirm the working directory. |
PermissionError |
The application cannot read, write, or move a file. |
Check access permissions and whether another process is locking the file. |
| Duplicate files |
A destination filename already exists. |
Skip the file, ask for confirmation, or generate a unique filename. |
| CSV parsing errors |
Malformed data or an unexpected file structure. |
Validate headers and rows and provide a clear error message. |
| Invalid decimal value |
Input contains non-numeric text or an unsupported format. |
Catch InvalidOperation and ask the user to enter a valid amount. |
| Incorrect expense totals |
Invalid rows, wrong filters, or incorrect amount conversions. |
Validate stored values and test calculations separately. |
| Changes are not saved |
The application exits before a write completes or writes to a different location. |
Use context managers and verify the absolute data-file path. |
9. Security and Data Integrity Best Practices
- Test file automation on copies of important data before running it on real folders.
- Use explicit source and destination paths instead of broad, uncontrolled directory operations.
- Never delete or overwrite files without a clear policy and appropriate safeguards.
- Validate all user-supplied input before using it in file operations.
- Keep backups of important expense records.
- Do not store banking passwords, payment card details, or other unnecessary sensitive information in plain-text project files.
- Use controlled file permissions when records contain personal information.
- Use a database such as SQLite when the project requires more reliable querying, updates, or multiple related tables.
- Keep business logic separate from input/output code to make testing easier.
10. Suggested Project Development Workflow
- Define the objective: Choose whether the application will automate files or manage expenses.
- List requirements: Decide the operations, inputs, outputs, and validation rules.
- Design the data model: Identify the fields and file structure required.
- Build the smallest working version: Implement one core operation first.
- Add persistence: Store information in CSV or JSON files.
- Add validation and exceptions: Handle invalid input and expected failures.
- Write tests: Verify normal and edge-case behavior.
- Improve usability: Add a menu, helpful messages, and configuration options.
- Document the project: Explain setup, usage, examples, and limitations.
- Prepare a portfolio: Add screenshots or sample output, a README, and a clear project description.
11. Project Extensions for Intermediate Learners
File Automation Extensions
- Build a graphical interface using Tkinter.
- Schedule periodic organization or backups.
- Generate file inventory reports in CSV format.
- Add a dry-run option and detailed logging.
- Detect duplicates using file hashes.
- Organize files recursively with user-controlled rules.
Expense Tracker Extensions
- Add edit and delete transaction operations with confirmation.
- Filter records by date range, category, or amount.
- Display monthly spending trends with Matplotlib.
- Add budget limits and alerts.
- Import transactions from an existing CSV file.
- Use SQLite for structured persistence.
- Create a graphical interface or a Flask-based web application.
- Add automated tests and a configurable currency display.
12. Interview Questions and Answers
Q1. What is file automation in Python?
Answer: File automation uses Python to perform repetitive filesystem tasks such as organizing, renaming, copying, moving, and reporting on files.
Q2. What is the purpose of pathlib?
Answer: pathlib provides an object-oriented interface for working with filesystem paths and includes methods for checking, listing, reading, writing, and creating files and directories.
Q3. What is the difference between shutil.copy() and shutil.move()?
Answer: shutil.copy() copies file content to another location, while shutil.move() moves a file or directory to a destination.
Q4. Why use the with statement for file handling?
Answer: The with statement manages the file resource and ensures it is closed when the block exits, including when an exception occurs.
Q5. What is CSV storage?
Answer: CSV stores tabular data as rows of values separated by delimiters. Python's csv module supports reading and writing this format.
Q6. Why is Decimal useful for an expense tracker?
Answer: Decimal supports decimal arithmetic that is better suited to monetary calculations than binary floating-point arithmetic. Inputs still need validation and a consistent precision policy.
Q7. How can you calculate total expenses?
Answer: Add the validated expense amounts, preferably using Decimal values and an explicit zero value such as Decimal("0.00").
Q8. How should missing files be handled?
Answer: Check whether the path exists and decide whether to create a new file, return an empty dataset, or display a meaningful error, depending on the application's requirements.
Q9. Why separate project code into functions?
Answer: Functions make code reusable, easier to understand, easier to test, and simpler to maintain.
Q10. How can you protect files during automation?
Answer: Validate paths, use dry-run mode, avoid overwriting existing files, test with sample data, and maintain backups before destructive operations.
Q11. When should an expense tracker use a database?
Answer: A database such as SQLite becomes useful when the application needs complex filtering, editing, relational data, or stronger transaction handling than a simple CSV file provides.
Q12. How can you present these projects in a portfolio?
Answer: Include a clear README, project requirements, setup instructions, sample output, screenshots if relevant, test cases, known limitations, and a description of the Python concepts used.
13. Practical Exercises
Exercise A: File Automation
- Create a test folder containing images, documents, spreadsheets, and text files.
- Write a script that creates category folders automatically.
- Move files based on their extensions.
- Skip existing destination files and log each operation.
- Add a dry-run option before performing actual moves.
Exercise B: Expense Tracker
- Create a CSV file for expense records.
- Implement an option to add a transaction.
- Implement an option to list all transactions.
- Calculate total spending and category totals.
- Add a monthly filter and budget check.
- Test invalid dates, invalid amounts, and empty datasets.
14. Quick Revision Checklist
- Understand the purpose and requirements of both projects.
- Use
pathlib for portable filesystem paths.
- Use
shutil for appropriate file-copying and moving operations.
- Use
csv or json for simple persistent storage.
- Use
Decimal for monetary calculations.
- Validate paths, dates, categories, descriptions, and amounts.
- Handle filesystem and data errors with useful messages.
- Separate calculations from file handling and user interaction.
- Test normal cases, invalid inputs, and edge cases.
- Document the project and provide reproducible setup instructions.
15. Conclusion
Building a Python File Automation System or Expense Tracker turns programming fundamentals into a useful application. File automation develops practical filesystem skills, while an expense tracker teaches data validation, persistent storage, calculations, and reporting. Both projects can be extended with databases, graphical interfaces, scheduling, visualizations, and automated tests. Focus on a working core application first, then improve its reliability, usability, and documentation.
16. Learn More with JustAcademy
Continue learning Python through the Python Training Course at JustAcademy. To explore course options, register for a course demo or visit the JustAcademy homepage.